using OpenBlock.Block;
using OpenBlock.Core;
using OpenBlock.Core.Event;
using OpenBlock.Core.Event.PlayerControl;
using OpenBlock.GUI;
using OpenBlock.Input;
using OpenBlock.Item;
using OpenBlock.Math;
using OpenBlock.Properties;
using OpenBlock.Terrain;
using OpenBlock.Terrain.BlockModels;
using OpenBlock.Utils;
using UnityEngine;
using UnityEngine.UI;

namespace OpenBlock.Entity.Player
{
    public class PlayerObj : MonoBehaviour
    {
        #region Inspector
        [SerializeField]
        public WorldObj world;
        [SerializeField]
        private SightIndicator sight;
        [SerializeField]
        private ItemShortcuts itemShortcuts;
        [SerializeField]
        private Button configureButton;
        [SerializeField]
        public float speed = 10;
        [SerializeField]
        private bool isCreativeMode;
        #endregion

        private Vector3 yawPitch;
        private bool digTrigger;
        private float digProgress = 0;

        private bool hasTarget = false;
        private Vector3Int prevTargetPos;
        private Vector3Int targetBlockPos;
        private Vector3Int readyPlaceBlockPos;
        private Vector3 readyPlaceBlockNormal;
        private Vector3 readyPlaceBlockCenter => new Vector3(readyPlaceBlockPos.x + 0.5f, readyPlaceBlockPos.y + 0.5f, readyPlaceBlockPos.z + 0.5f);
        private Vector2 placeBlockDirection
        {
            get
            {
                var centerToPlayer = transform.position - readyPlaceBlockCenter;
                return new Vector2(centerToPlayer.x, centerToPlayer.z).normalized;
            }
        }

        private int carriedItemIdx;
        private ItemStack[] itemStacks;

        private void Start()
        {
            GameManager.Instance.eventQueue.RegisterHandler(GameEventType.PlayerControl, OnPlayerControl);

            Camera.main.GetComponent<MainCamera>().Trace(gameObject);

            itemStacks = new ItemStack[6];
            itemStacks[0] = new ItemStack(new ItemState(ItemId.BlockItem).AddProperty("block", new Property((uint)BlockId.Furnance)));
            itemStacks[1] = new ItemStack(new ItemState(ItemId.Brush).AddProperty("RGB", new Property(0xffffffff)));
            itemStacks[2] = new ItemStack(new ItemState(ItemId.BlockItem).AddProperty("block", new Property((uint)BlockId.CraftingTable)));
            itemStacks[3] = new ItemStack(new ItemState(ItemId.BlockItem).AddProperty("block", new Property((uint)BlockId.TNT)));
            itemStacks[4] = new ItemStack(new ItemState(ItemId.BlockItem).AddProperty("block", new Property((uint)BlockId.Log)));
            itemStacks[5] = new ItemStack(new ItemState(ItemId.BlockItem).AddProperty("block", new Property((uint)BlockId.Grass)));
            for (int i = 0; i < itemStacks.Length; i++)
            {
                itemShortcuts.SetItem(i, itemStacks[i]);
            }
        }

        public void OnPlayerControl(IGameEvent control)
        {
            if (GameManager.Instance.GetGameStage() != GameManager.GameStage.Game) return;

            switch (control)
            {
                case LookEvent look:
                    Look(look.delta);
                    break;

                case MoveEvent move:
                    Move(move.direction);
                    break;

                case JumpEvent _:
                    Jump();
                    break;

                case DescendEvent _:
                    Descend();
                    break;

                case PlaceEvent _:
                    Place();
                    break;

                case DigEvent dig:
                    if (dig.phase == DigEvent.Phase.Start)
                    {
                        DigStart();
                    }
                    else
                    {
                        DigEnd();
                    }
                    break;

                case SelectItemEvent selectItem:
                    if (selectItem.mode == SelectItemEvent.SelectMode.Offset)
                    {
                        carriedItemIdx -= selectItem.value;
                        carriedItemIdx += itemShortcuts.count;
                        carriedItemIdx %= itemShortcuts.count;
                    }
                    else
                    {
                        carriedItemIdx = selectItem.value;
                    }

                    itemShortcuts.SetIndex(carriedItemIdx);

                    if (InputManager.Instance.ctrlMode == ControlMode.Touch) 
                    {
                        var itemStack = itemStacks[carriedItemIdx];
                        var item = itemStack.Item;
                        if (ItemRegistry.items[item.id] is IConfigurableItem)
                        {
                            configureButton.gameObject.SetActive(true);
                        }
                        else
                        {
                            configureButton.gameObject.SetActive(false);
                        }
                    }
                    break;

                case ConfigureItemEvent _:
                    {
                        var itemStack = itemStacks[carriedItemIdx];
                        var item = itemStack.Item;
                        if (ItemRegistry.items[item.id] is IConfigurableItem ci)
                        {
                            ci.OnConfigue(item);
                        }
                    }
                    break;
            }
        }

        private void Update()
        {
            RaycastChunk();
            if (digTrigger)
            {
                if (hasTarget)
                {
                    if (!isCreativeMode)
                    {
                        BlockId id = world.level.GetBlockState(targetBlockPos).id;
                        if (id != BlockId.Air)
                        {
                            IBlock block = BlockRegistry.blocks[id];
                            if (prevTargetPos == targetBlockPos)
                            {
                                digProgress += Time.deltaTime;
                            }
                            else
                            {
                                digProgress = 0;
                            }

                            if (digProgress >= block.Hardness)
                            {
                                world.level.DestroyBlock(targetBlockPos);
                                digProgress = 0;
                            }

                            sight.SetDigProgress(digProgress, block.Hardness);
                        }
                    }
                    else
                    {
                        world.level.DestroyBlock(targetBlockPos);
                        digTrigger = false;
                    }
                }
                else
                {
                    digProgress = 0;
                }
            }
        }

        private void LateUpdate()
        {
            prevTargetPos = targetBlockPos;
        }

        public void RaycastChunk()
        {
            Ray sightRay = Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));
            if (Physics.Raycast(sightRay, out RaycastHit hit, 10, LayerMask.GetMask("Chunk")))
            {
                targetBlockPos = MathUtils.GetBlockPos(hit.point, hit.normal);
                readyPlaceBlockPos = targetBlockPos + MathUtils.AsBlockPos(hit.normal);
                readyPlaceBlockNormal = hit.normal;

                GameManager.Instance.debugText.text = $"{targetBlockPos}\n{MathUtils.BlockPos2ChunkPos(targetBlockPos)}\n";
                GameManager.Instance.debugText.text += world.level.GetBlockState(targetBlockPos).ToString();

                BlockIndicator.Draw(targetBlockPos, gameObject.layer, Camera.main);

                hasTarget = true;
            }
            else
            {
                DigEnd();
                hasTarget = false;
            }
        }

        #region Input Behaviours
        public void Jump()
        {
            transform.position += speed * Time.deltaTime * Vector3.up;
        }

        public void Descend()
        {
            transform.position += speed * Time.deltaTime * Vector3.down;
        }

        public void DigStart()
        {
            if (hasTarget) digTrigger = true;
        }

        public void DigEnd()
        {
            digTrigger = false;
            digProgress = 0;
            sight.SetDigProgress(0);
        }

        public void Place()
        {
            if (hasTarget)
            {
                sight.OnPlaceBlock();

                BlockState target = world.level.GetBlockState(targetBlockPos);
                ItemStack carriedItem = itemStacks[carriedItemIdx];

                if (target.id != BlockId.Air && BlockRegistry.blocks[target.id] is IUsableBlock block)
                {
                    block.OnUse(target);
                }
                else if (ItemRegistry.items[carriedItem.Item.id] is IUsableItem item)
                {
                    item.OnUse(carriedItem, target, this, readyPlaceBlockPos);
                }
                
            }
        }

        public void Move(Vector2 movement)
        {
            var forward = new Vector3(transform.forward.x, 0, transform.forward.z).normalized;
            var right = new Vector3(forward.z, 0, -forward.x);
            var moveDir = movement.y * forward + movement.x * right;

            transform.position += speed * Time.deltaTime * moveDir;
        }

        public void Look(Vector2 delta)
        {
            yawPitch += new Vector3(-delta.y, delta.x);
            yawPitch.x = Mathf.Clamp(yawPitch.x, -89, 89);

            transform.localRotation = Quaternion.Euler(yawPitch);
        }
        #endregion

        public void PlaceBlock(BlockId blockId)
        {
            if (blockId == BlockId.Air) return;
            var blockState = new BlockState(blockId);
            switch (blockId)
            {
                case BlockId.CraftingTable:
                case BlockId.Furnance:
                    var pDir = placeBlockDirection;
                    if (pDir.y > MathUtils.SqrtTwoOverTwo && Mathf.Abs(pDir.x) < MathUtils.SqrtTwoOverTwo)
                    {
                        blockState.AddProperty("dir", new Property((byte)Direction.North));
                    }
                    else if (pDir.y < -MathUtils.SqrtTwoOverTwo && Mathf.Abs(pDir.x) < MathUtils.SqrtTwoOverTwo)
                    {
                        blockState.AddProperty("dir", new Property((byte)Direction.South));
                    }
                    else if (Mathf.Abs(pDir.y) < MathUtils.SqrtTwoOverTwo && pDir.x > MathUtils.SqrtTwoOverTwo)
                    {
                        blockState.AddProperty("dir", new Property((byte)Direction.East));
                    }
                    else if (Mathf.Abs(pDir.y) < MathUtils.SqrtTwoOverTwo && pDir.x < -MathUtils.SqrtTwoOverTwo)
                    {
                        blockState.AddProperty("dir", new Property((byte)Direction.West));
                    }
                    break;

                case BlockId.Log:
                    if (Mathf.Approximately(Mathf.Abs(Vector3.Dot(readyPlaceBlockNormal, Vector3.right)), 1))
                    {
                        blockState.AddProperty("axis", new Property((byte)Axis.X));
                    }
                    else if (Mathf.Approximately(Mathf.Abs(Vector3.Dot(readyPlaceBlockNormal, Vector3.forward)), 1))
                    {
                        blockState.AddProperty("axis", new Property((byte)Axis.Z));
                    }
                    else
                    {
                        blockState.AddProperty("axis", new Property((byte)Axis.Y));
                    }
                    break;
            }
            world.level.SetBlock(blockState, readyPlaceBlockPos);
        }

        private void OnDestroy()
        {
            GameManager.Instance.eventQueue.RemoveHandler(GameEventType.PlayerControl, OnPlayerControl);
        }
    }

}